/**
 * 
 */
package oop.ex2.parser;
import java.util.HashMap;
import oop.ex2.filters.exceptions.*;
import java.util.StringTokenizer;
import java.util.ArrayList;
import oop.ex2.filters.*;
import oop.ex2.parser.exceptions.*;
/**
 * @author liorz06
 * @author ohad.dan
 *this class represents the parser of the expression.
 */
public class Parser {
	//***********************************************************************
	// Finals :                                                             *
	//***********************************************************************
	private static final String ANDSTR = "AND";
    private static final String ORSTR = "OR";
    private static final String NOTSTR = "NOT";
    private String _exp;
    private static final String fWHITESPACE_AND_QUOTES = " \t\r\n\"";
    private static final char WHITESPACE = ' ';
    private ArrayList<String> _list;
    public static final char LEFTBRACKET ='(';
    public static final char QUOTES = '"';
    public static final char RIGHTBRACKET = ')';
    //***********************************************************************
	//  Data members :                                                      *
	//***********************************************************************
    private HashMap<String, LogicOperator> _logicOps;
    //***********************************************************************
	//  Methods :                                                           *
	//***********************************************************************
    /**
     * Constructs a new parser.
     * @param exp The expression to parse.
     * @throws ParsingException in case of error while parsing.
     */
    public Parser( String exp ) throws ParsingException {
        _exp = exp;
        initializeLogicOps();
        createTokenList();
        
        if ( !checkInitialValidity() ){
            throw new ParsingException("Error:Invalid filter expression");
        }
    }
    /**
     * Checks basic expression validity.
     * @return True if the expression is initially valid, false otherwise.
     * @throws ParsingException in case of error while parsing.
     */
    private boolean checkInitialValidity() throws ParsingException  {
        int operatorPosition = getTopOperatorPosition(_list);
        return ( wrongBrackets() && validLonelyBrackets() && 
                ((operatorPosition ==-1 && _list.size() ==1) ||
                        operatorPosition != -1 && _list.size()>1));
    }
    /**
     * Creates the token list.
     */
    private void createTokenList() {
        _list = new ArrayList<String>();
        StringTokenizer tokenizer = 
            new StringTokenizer( _exp, fWHITESPACE_AND_QUOTES, true );
        String token;
        while ( tokenizer.hasMoreTokens() ) {
            token = tokenizer.nextToken();
            if ( token.equals("\"") ) {
                String quotedStr = token;
                if ( tokenizer.hasMoreTokens() ) {
                    token = tokenizer.nextToken();
                }
                while( !token.equals("\"") && tokenizer.hasMoreTokens()) {
                    quotedStr+=token;
                    token = tokenizer.nextToken();
                }
                _list.add(_list.remove(_list.size()-1) + quotedStr+token);
                if ( tokenizer.hasMoreTokens() ) {
                    token = tokenizer.nextToken();
                }
                if ( token.contains(RIGHTBRACKET+"" ) ) {
                    _list.add(_list.remove(_list.size()-1)+token);
                }
            }else if ( !token.equals(" ")){ 
                _list.add(token);
            }
        }
    }
    /**
     * Parse the expression.
     * @return Value of the expression.
     * @throws ParsingException in case of error while parsing.
     * @throws FilterException in case of error of filters.
     */
    public Conditionable parse() throws ParsingException, FilterException {
        return parse(_list);
    }
    /**
     * Parse the expression.
     * @param tokens The list of tokens.
     * @return Value of the expression.
     * @throws ParsingException in case of error while parsing.
     * @throws FilterException in case of error of filters.
     */
    private Conditionable parse(ArrayList<String> tokens) throws 
    ParsingException, FilterException{

        if ( tokens == null || tokens.isEmpty() ) {
            return null;
        }
        int topOperator = getTopOperatorPosition(tokens);
        if (tokens.size() == 1) {
            Filter newFilter = getNewFilter(tokens.get(0));
            if ( newFilter == null ) {
                throw new UnSupportedFilterException("Error:Unsupported filter");
            }
            return newFilter;
        }
        LogicOperator newOperator = getOperatorByPos(topOperator, tokens);
        newOperator.setLeftChild(parse(splitTokenArr(tokens, 0, topOperator)));
        newOperator.setRightChild(parse(splitTokenArr(tokens, topOperator+1,
                tokens.size())));
        return newOperator;
    }
    /**
     * Splits the token list into the two most upper expressions.
     * @param tokens Token list.
     * @param start Start point.
     * @param end End point.
     * @return Splitted token list.
     */
    private ArrayList<String> 
    splitTokenArr(ArrayList<String> tokens, int start, int end) {
        ArrayList<String> splitted = new ArrayList<String>(); 
        if ( start == end ) {
            return null;
        }
        if (end-start == 1 ) {
            splitted.add(tokens.get(start));
            return splitted;
        }
        
        for (String token: tokens.subList(start, end) ) {
            splitted.add(token);
        }
        return splitted;
    }
    /**
     * Returns top operator's position.
     * @param tokens Token list.
     * @return Top operator's position.
     * @throws ParsingException in case of error while parsing.
     */
    private int getTopOperatorPosition( ArrayList<String> tokens )
    throws ParsingException{
        int pos=-1;
        int bracketMin=_exp.length();
        int bracketCounter=0;
        int operatorCounter = 0;
        int iterationCounter = 0;
        for( String tokenStr:tokens ){
            boolean isFilter = tokenIsFilter(tokenStr);
            boolean isOperator = isOperator(tokenStr);
            if ( isOperator ) {
                operatorCounter++;
            }
            for( int j=0; j<tokenStr.length(); j++){
                if ( !tokenStr.startsWith(LEFTBRACKET+"") && 
                        !tokenStr.endsWith(RIGHTBRACKET+"")){
                    break;
                }
                if ( tokenStr.charAt(j) == LEFTBRACKET) {
                    bracketCounter++;
                    if ( iterationCounter!= 0 ) {
                        operatorCounter = 0;
                    }
                }else if ( tokenStr.charAt(j) == RIGHTBRACKET ) {
                    bracketCounter--;
                    operatorCounter = 0;
                }
            }
            if ( operatorCounter>1 ) {
                throw new ParsingException("Error:Invalid filter expression");
            }
            if ( isFilter && bracketMin>bracketCounter ) {
                pos = tokens.indexOf(tokenStr);
                bracketMin = bracketCounter;
                if ( bracketMin == 0 ) {
                    throw new ParsingException("Error:Invalid filter expression");
                }
            }
            iterationCounter++;
        }
        return pos;
    }
    /**
     * Initializes logic operators. 
     */
    private void initializeLogicOps() {
        _logicOps = new HashMap<String, LogicOperator>();
        _logicOps.put(ANDSTR, new ANDOperator());
        _logicOps.put(ORSTR, new OROperator());
        _logicOps.put(NOTSTR, new NOTOperator());
    }
    /**
     * Returns an operator by position.
     * @param pos Position of desired operator.
     * @param tokens Token list.
     * @return The operator indicated by given position.
     * @throws ParsingException In case of error while parsing.
     */
    private LogicOperator getOperatorByPos(int pos, ArrayList<String> tokens) 
    throws ParsingException{
        if ( pos == -1 ) {
            throw new ParsingException("Error:Invalid filter expression");
        }
        String newToken = tokens.get(pos).replaceAll("\\"+RIGHTBRACKET+"|\\"+LEFTBRACKET, "");
        if ( _logicOps.get(newToken) != null ) {
            return _logicOps.get(newToken).clone();
        }
        return null;
    }
    /**
     * Returns a new filter based on the string passed as argument.
     * @param exp
     * @return filter
     * @throws ParsingException
     * @throws FilterException
     */
    private Filter getNewFilter( String exp ) 
    throws ParsingException, FilterException{
        if ( !exp.contains(":") ) {
            return null;
        }
        if ( exp.endsWith(":") ) {
            throw new ParsingException("Error:Invalid filter expression");
        }else if ( exp.endsWith(RIGHTBRACKET+"") && 
                exp.startsWith(LEFTBRACKET+"") ){
            throw new ParsingException("Error:Invalid filter expression");
        }
        String[] splitted = exp.split(":");
        String val = splitted[1].replaceAll("\\"+RIGHTBRACKET+"|\\"+LEFTBRACKET, "");
        String filterExp = splitted[0].replaceAll("\\"+RIGHTBRACKET+"|\\"+LEFTBRACKET, "");
        FilterFactory factory = new FilterFactory();
        Filter filter = factory.getInstance(filterExp);
        if ( filter == null ) {
            throw new UnSupportedFilterException("Error:Unsupported filter");
        }else if ( val == null ) {
            throw new FilterDataException("Error:Invalid filter expression");
        }
        filter.setData(val);
        return filter;
    }
    /**
     * checks whether the token is a filter
     * @param token
     * @return true in case the token is a filter
     */
    private boolean tokenIsFilter(String token) {
        String newToken = token.replaceAll("\\"+RIGHTBRACKET+"|\\"+LEFTBRACKET, "");
        if ( _logicOps.get(newToken) != null ) {
                return true;
        }
        return false;
    }
    /**
     * checks whether the token is an operator.
     * @param token
     * @return true if the token is an operator
     * @throws ParsingException
     */
    private boolean isOperator(String token) throws ParsingException {
        ArrayList<String> tempList = new ArrayList<String>();
        tempList.add(token);
        if ( getOperatorByPos(0, tempList) != null){
            return true;
        }
        return false;
    }
    /**
     * checks the validity of the bracket composition.
     * @return true if the brackets are valid
     */
    private boolean validLonelyBrackets() {
        //checks if the expression contains only filter and brackets
        if ( _list.size() == 1 && (_exp.startsWith(LEFTBRACKET+"") 
                || _exp.endsWith(RIGHTBRACKET+""))){
            return false;
        //checks if the expression doesn't contain brackets when it needs.
        }else if ( !_exp.startsWith(LEFTBRACKET+"") && 
                !_exp.endsWith(RIGHTBRACKET+"") && _list.size()>1 ) {
            return false;
        }
        return true;
    }
    private int nextQuotes(int pos, String exp) {
        if ( !exp.substring(pos).contains(QUOTES+""+"") ) {
            return -1;
        }
        return exp.substring(pos+1).indexOf(QUOTES+"")+pos+1;
    }
    /**
     * Checks another variation of bracket composition.
     * @return true for a valid composition.
     * @throws ParsingException
     */
    private boolean wrongBrackets() throws ParsingException{
        int bracketCounter=0;
        int quoteCounter=0;
        for ( int i=0; i<_exp.length(); i++ ){
            if ( _exp.charAt(i) == QUOTES ) {
                i = nextQuotes(i, _exp);
                if ( i == -1) {
                    return false;
                }else {
                    quoteCounter+=2;
                }
            }
            if ( _exp.charAt(i) == RIGHTBRACKET ) {
                bracketCounter--;
            }else if ( _exp.charAt(i) == LEFTBRACKET ) {
                if ( i>0  && _exp.charAt(i-1)!=LEFTBRACKET
                        && _exp.charAt(i-1) != WHITESPACE ) {
                    return false;
                }
                bracketCounter++;
            }
        }
        if( quoteCounter % 2 != 0 ){
            throw new ParsingException("Error:Invalid filter parameters");
        }
        return bracketCounter==0;
    }
}